#define LOG_TAG "[KV_DEMO]"

#include "log_print.h"
#include "distributed_kv_data_manager.h"
#include "kvstore_sync_callback.h"
#include "directory_ex.h"
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string>
#include <memory>
#include <iostream>
#include <sstream>
#include <memory>

const int FILE_PERMISSION = 0777;

using namespace OHOS::DistributedKv;

class DeviceSyncCallbackImpl : public KvStoreSyncCallback {
public:
    void SyncCompleted(const std::map <std::string, Status> &results) override {
        ZLOGI("-----sync-----");
    }

    ~DeviceSyncCallbackImpl() {}
};

class KvStoreObserverUnitTest : public KvStoreObserver {
public:
    std::vector <Entry> insertEntries_;
    std::vector <Entry> updateEntries_;
    std::vector <Entry> deleteEntries_;
    bool isClear_;

    KvStoreObserverUnitTest();

    ~KvStoreObserverUnitTest() {}

    KvStoreObserverUnitTest(const KvStoreObserverUnitTest &) = delete;

    KvStoreObserverUnitTest &operator=(const KvStoreObserverUnitTest &) = delete;

    KvStoreObserverUnitTest(KvStoreObserverUnitTest &&) = delete;

    KvStoreObserverUnitTest &operator=(KvStoreObserverUnitTest &&) = delete;

    void OnChange(const ChangeNotification &changeNotification);

    // reset the callCount_ to zero.
    void ResetToZero();

    uint64_t GetCallCount() const;

private:
    uint64_t callCount_;
};

KvStoreObserverUnitTest::KvStoreObserverUnitTest() {
    callCount_ = 0;
    insertEntries_ = {};
    updateEntries_ = {};
    deleteEntries_ = {};
    isClear_ = false;
}

void KvStoreObserverUnitTest::OnChange(const ChangeNotification &changeNotification) {
    ZLOGD("begin.");
    callCount_++;
    std::cout << "database changed , callCount is:" << callCount_ << std::endl;
}

void KvStoreObserverUnitTest::ResetToZero() {
    callCount_ = 0;
}

class KvStoreTest {
public:
    KvStoreTest() {
        appId = {"app_demo2"};
        storedId = {"store_demo2"};

        options.autoSync = true;
        options.kvStoreType = SINGLE_VERSION;
        options.encrypt = false;
        options.createIfMissing = true;
        options.baseDir = std::string("/data/app/el1/0/database/app_demo2");
        options.area = EL1;
        mkdir(options.baseDir.c_str(), (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH));
        OHOS::ChangeModeDirectory(options.baseDir, FILE_PERMISSION);

        Status result = kvDataManager.GetSingleKvStore(options, appId, storedId, kvStore);
        ZLOGI("Get kvStore instance result is: %d", result);


        kvDataManager.GetDeviceList(deviceInfos_, DeviceFilterStrategy::NO_FILTER);
        ZLOGI("*****GetDeviceList result is %d", result);

        baseDir = "/data/app/el1/0/database/app_demo2/";
    }

    void Put(std::string key, std::string value) {
        Status result = kvStore->Put(key, value);
        ZLOGI("Put result is %d", result);
    }

    std::string Get(std::string key) {
        OHOS::DistributedKv::Value v;
        Status result = kvStore->Get(key, v);
        ZLOGI("Get result is %d", result);
        return v.ToString();
    }

    void Delete(std::string key) {
        Status result = kvStore->Delete(key);
        ZLOGI("Delete result is %d", result);
    }

    void Commit() {
        Status result = kvStore->Commit();
        ZLOGI("Commit result is %d", result);
    }

    void Rollback() {
        Status result = kvStore->Rollback();
        ZLOGI("Rollback result is %d", result);
    }

    void PutBatch(const std::vector <Entry> &entries) {
        Status result = kvStore->PutBatch(entries);
        ZLOGI("PutBatch result is %d", result);
        if (result == 0)
            std::cout << "PutBatch success" << std::endl;
    }

    void DeleteBatch(const std::vector <Key> &keys) {
        Status result = kvStore->DeleteBatch(keys);
        ZLOGI("DeleteBatch result is %d", result);
    }

    void GetEntries(const Key &prefix, std::vector <Entry> &entries) {
        Status result = kvStore->GetEntries(prefix, entries);
        ZLOGI("GetEntries result is %d", int(result));
        if (result == 0) {
            std::cout << "get result is:" << std::endl;
            for (int i = 0; i < int(entries.size()); ++i) {
                std::cout << entries[i].key.ToString() << " " << entries[i].value.ToString() << std::endl;
            }
        }
    }

    void GetResultSet(const Key &prefix) {
        std::shared_ptr <KvStoreResultSet> resultSet;
        Status result = kvStore->GetResultSet({prefix}, resultSet);
        ZLOGI("*****GetResultSet result is %d", result);
        std::cout << "find " << resultSet->GetCount() << " in database" << std::endl;

    }

    void StartTransaction() {
        Status result = kvStore->StartTransaction();
        ZLOGI("*****StartTransaction result is %d", result);
    }

    void Sync(std::string mode, std::string time) {
        uint32_t allowedDelayMs = atoi(time.c_str());
        std::vector <std::string> deviceIds = {deviceInfos_[0].deviceId};
        std::cout << "deviceIds:" << deviceInfos_[0].deviceId << std::endl;
        if (mode == "push")
            kvStore->Sync(deviceIds, SyncMode::PUSH, allowedDelayMs);
        else if (mode == "pull")
            kvStore->Sync(deviceIds, SyncMode::PULL, allowedDelayMs);
        else
            kvStore->Sync(deviceIds, SyncMode::PUSH_PULL, allowedDelayMs);
    }

    void SetSyncParam(int param) {
        KvSyncParam syncParam{param}; // 500ms
        ZLOGI("*****syncParam is  %d", param);
        auto ret = kvStore->SetSyncParam(syncParam);
        ZLOGI("*****SetSyncParam result is %d", ret);
    }

    void GetSyncParam() {
        KvSyncParam syncParamRet;
        auto ret = kvStore->GetSyncParam(syncParamRet);
        ZLOGI("*****GetSyncParam result is %d", ret);
        std::cout << "get sync allow delay " << syncParamRet.allowedDelayMs << "(ms)" << std::endl;
    }

    void RegisterSyncCallback() {
        auto syncCallback = std::make_shared<DeviceSyncCallbackImpl>();
        auto status = kvStore->RegisterSyncCallback(syncCallback);
        ZLOGI("*****RegisterSyncCallback result is %d", status);
    }

    void UnRegisterSyncCallback() {
        auto status = kvStore->UnRegisterSyncCallback();
        ZLOGI("*****UnRegisterSyncCallback result is %d", status);
    }

    void SubscribeKvStore() {
        auto subStatus = kvStore->SubscribeKvStore(SubscribeType::SUBSCRIBE_TYPE_ALL, observer);
        ZLOGI("*****SubscribeKvStore result is %d", subStatus);
    }

    void UnSubscribeKvStore() {
        auto subStatus = kvStore->UnSubscribeKvStore(SubscribeType::SUBSCRIBE_TYPE_LOCAL, observer);
        ZLOGI("*****UnSubscribeKvStore result is %d", subStatus);
    }

    void Backup(std::string file) {
        auto result = kvStore->Backup(file, baseDir);
        ZLOGI("*****Backup result is %d", result);
    }

    void Restore(std::string file) {
        auto result = kvStore->Restore(file, baseDir);
        ZLOGI("*****Restore result is %d", result);
    }

    void DeleteBackup(std::vector <std::string> &files) {
        std::map <std::string, OHOS::DistributedKv::Status> results;
        auto status = kvStore->DeleteBackup(files, baseDir, results);
        ZLOGI("*****DeleteBackup result is %d", status);
        for (auto &t : results) {
            std::cout << "key:" << t.first << " value:" << t.second << std::endl;
        }

    }

    void GetStoreId() {
        std::string storeid = kvStore->GetStoreId();
        ZLOGI("*****GetStoreId is %s", storeid.c_str());
    }

    void SetCapabilityEnabled(bool condition) {
        ZLOGI("*****condition  is %d", condition);
        auto status = kvStore->SetCapabilityEnabled(condition);
        ZLOGI("*****SetCapabilityEnabled result is %d", status);
    }

    void SubscribeWithQuery(std::string deviceId, std::string condition) {
        ZLOGI("*****condition is %s", condition.c_str());
        std::vector <std::string> deviceIds;
        if (deviceId == "") { deviceIds = {deviceInfos_[0].deviceId}; }
        else { deviceIds = {deviceId}; }
        DataQuery dataQuery;
        dataQuery.KeyPrefix(condition);
        auto syncStatus = kvStore->SubscribeWithQuery(deviceIds, dataQuery);
        ZLOGI("*****SubscribeWithQuery result is %d", syncStatus);
    }

    void UnSubscribeWithQuery(std::string deviceId, std::string condition) {
        ZLOGI("*****condition is %s", condition.c_str());
        std::vector <std::string> deviceIds;
        if (deviceId == "") { deviceIds = {deviceInfos_[0].deviceId}; }
        else { deviceIds = {deviceId}; }
        DataQuery dataQuery;
        dataQuery.KeyPrefix(condition);
        auto syncStatus = kvStore->UnsubscribeWithQuery(deviceIds, dataQuery);
        ZLOGI("*****UnsubscribeWithQuery result is %d", syncStatus);
    }

    void SetCapabilityRange(std::vector <std::string> local, std::vector <std::string> remote) {
        std::cout << "local  range is : ";
        for (int i = 0; i < 2; ++i) {
            std::cout << local[i] << " ";
        }
        std::cout << "\nremote  range is : ";
        for (int i = 0; i < 2; ++i) {
            std::cout << remote[i] << " ";
        }
        std::cout << std::endl;
        auto testStatus = kvStore->SetCapabilityRange(local, remote);
        ZLOGI("*****SetCapabilityRange result is %d", testStatus);
    }

    void GetAllKvStoreId(std::string appid) {
        OHOS::DistributedKv::AppId appId_tmp={appid} ;
        std::vector <StoreId> storeIds;
        kvDataManager.GetAllKvStoreId(appId_tmp, storeIds);
        ZLOGI("*****storeIds.size():%d", storeIds.size());
        for (int i = 0; i < storeIds.size(); ++i) {
            ZLOGI("*****storeIds %d is %s", i,storeIds[i].storeId.c_str());
        }
    }

    void GetSingleKvStore(std::string appid,std::string storeid) {
        appId = {appid};
        storedId = {storeid};
        std::shared_ptr <OHOS::DistributedKv::SingleKvStore> kvStoreNew;
        Status status = kvDataManager.GetSingleKvStore(options, appId, storedId, kvStoreNew);
        ZLOGI("*****GetSingleKvStore result is %d", status);
    }

    void CloseKvStore(std::string appid,std::string storeid) {
        OHOS::DistributedKv::AppId appId_tmp={appid} ;
        OHOS::DistributedKv::StoreId storedId_tmp={storeid} ;
        Status status = kvDataManager.CloseKvStore(appId_tmp, storedId_tmp);
        ZLOGI("*****CloseKvStore result is %d", status);
    }

    void CloseAllKvStore(std::string appid) {
        OHOS::DistributedKv::AppId appId_tmp={appid} ;
        Status status = kvDataManager.CloseAllKvStore(appId_tmp);
        ZLOGI("*****CloseAllKvStore result is %d", status);
    }

    void DeleteKvStore(std::string appid,std::string storeid) {
        OHOS::DistributedKv::AppId appId_tmp={appid} ;
        OHOS::DistributedKv::StoreId storedId_tmp={storeid} ;
        Status status = kvDataManager.DeleteKvStore(appId_tmp, storedId_tmp, baseDir);
        ZLOGI("*****CloseAllKvStore result is %d", status);
    }

    void DeleteAllKvStore(std::string appid) {
        OHOS::DistributedKv::AppId appId_tmp={appid} ;
        Status status = kvDataManager.DeleteAllKvStore(appId_tmp, baseDir);
        ZLOGI("*****DeleteAllKvStore result is %d", status);
    }

    void GetLocalDevice() {
        DeviceInfo dvInfo;
        Status status = kvDataManager.GetLocalDevice(dvInfo);
        ZLOGI("*****GetLocalDevice result is %d", status);
        ZLOGI("*****device id is : %s", dvInfo.deviceId.c_str());
    }


private:
    std::shared_ptr <OHOS::DistributedKv::SingleKvStore> kvStore;
    std::vector <DeviceInfo> deviceInfos_;
    std::string baseDir;
    OHOS::DistributedKv::DistributedKvDataManager kvDataManager;
    OHOS::DistributedKv::AppId appId ;
    OHOS::DistributedKv::StoreId storedId ;
    OHOS::DistributedKv::Options options;
    std::shared_ptr<KvStoreObserverUnitTest> observer = std::make_shared<KvStoreObserverUnitTest>();

};

void Run() {
    KvStoreTest kvs;
    std::string line;
    std::cout << "DISTRIBUTEDDATAMGR_ERR_OFFSET:" << OHOS::DistributedKv::DISTRIBUTEDDATAMGR_ERR_OFFSET
              << std::endl;//27459584
    while (std::getline(std::cin, line)) {
        std::istringstream iss(line);
        std::string cmd;
        iss >> cmd;
        if (cmd == "get") {
            std::string key;
            std::getline(iss, key, '"');
            std::getline(iss, key, '"');
            std::string value = kvs.Get(key);
            std::cout << "Get value is: " << value << std::endl;
        } else if (cmd == "put") {
            std::string key, value;
            std::getline(iss, key, '"');
            std::getline(iss, key, '"');
            std::getline(iss, value, '"');
            std::getline(iss, value, '"');
            kvs.Put(key, value);
            std::cout << "Put value success" << std::endl;
        } else if (cmd == "delete") {
            std::string key;
            std::getline(iss, key, '"');
            std::getline(iss, key, '"');
            kvs.Delete(key);
        } else if (cmd == "commit") {
            kvs.Commit();
        } else if (cmd == "rollback") {
            kvs.Rollback();
        } else if (cmd == "putbatch") {//putbatch "2" "test100" "testxxx100" "test200" "testxxx200"
            std::string count;
            std::vector <Entry> entries;
            Entry entry;
            std::string key, value;
            std::getline(iss, count, '"');
            std::getline(iss, count, '"');
            ZLOGI("count value is %s", count.c_str());
            for (int i = 0; i < atoi(count.c_str()); ++i) {
                std::getline(iss, key, '"');
                std::getline(iss, key, '"');
                std::getline(iss, value, '"');
                std::getline(iss, value, '"');
                ZLOGI("key value is %s", key.c_str());
                ZLOGI("value value is %s", value.c_str());
                entry.key = key;
                entry.value = value;
                entries.push_back(entry);
            }
            kvs.PutBatch(entries);
        } else if (cmd == "deletebatch") {
            std::string count;
            std::string key;
            std::vector <Key> keys;
            std::getline(iss, count, '"');
            std::getline(iss, count, '"');
            for (int i = 0; i < atoi(count.c_str()); ++i) {
                std::getline(iss, key, '"');
                std::getline(iss, key, '"');
                keys.push_back(key);
            }
            kvs.DeleteBatch(keys);
        } else if (cmd == "getentries") {
            std::string key;
            std::getline(iss, key, '"');
            std::getline(iss, key, '"');
            const Key prefix = key;
            std::vector <Entry> entriesID;
            kvs.GetEntries(prefix, entriesID);
        } else if (cmd == "exit" || cmd == "quit") {
            break;
        } else if (cmd == "getresultset") {
            std::string key;
            std::getline(iss, key, '"');
            std::getline(iss, key, '"');
            const Key prefix = key;
            kvs.GetResultSet(prefix);
        } else if (cmd == "starttransaction") {
            kvs.StartTransaction();
        } else if (cmd == "sync") {
            std::string mode;
            std::getline(iss, mode, '"');
            std::getline(iss, mode, '"');
            std::string time;
            std::getline(iss, time, '"');
            std::getline(iss, time, '"');
            kvs.Sync(mode, time);
        } else if (cmd == "setsyncparam") {
            std::string param;
            std::getline(iss, param, '"');
            std::getline(iss, param, '"');
            kvs.SetSyncParam(atoi(param.c_str()));
        } else if (cmd == "getsyncparam") {
            kvs.GetSyncParam();
        } else if (cmd == "registersync") {
            kvs.RegisterSyncCallback();
        } else if (cmd == "unregistersync") {
            kvs.UnRegisterSyncCallback();
        } else if (cmd == "SubscribeKvStore") {
            kvs.SubscribeKvStore();
        } else if (cmd == "UnSubscribeKvStore") {
            kvs.UnSubscribeKvStore();
        } else if (cmd == "Backup") {
            std::string file;
            std::getline(iss, file, '"');
            std::getline(iss, file, '"');
            kvs.Backup(file);
        } else if (cmd == "Restore") {
            std::string file;
            std::getline(iss, file, '"');
            std::getline(iss, file, '"');
            kvs.Restore(file);
        } else if (cmd == "DeleteBackup") {
            std::string count;
            std::string file;
            std::vector <std::string> files;
            std::getline(iss, count, '"');
            std::getline(iss, count, '"');
            for (int i = 0; i < atoi(count.c_str()); ++i) {
                std::getline(iss, file, '"');
                std::getline(iss, file, '"');
                files.push_back(file);
            }
            kvs.DeleteBackup(files);
        } else if (cmd == "GetStoreId") {
            kvs.GetStoreId();
        } else if (cmd == "SetCapabilityEnabled") {
            std::string condition;
            std::getline(iss, condition, '"');
            std::getline(iss, condition, '"');
            if (condition == "open")
                kvs.SetCapabilityEnabled(true);
            else
                kvs.SetCapabilityEnabled(false);
        } else if (cmd == "SubscribeWithQuery") {
            std::string deviceId;
            std::getline(iss, deviceId, '"');
            std::getline(iss, deviceId, '"');
            std::string condition;
            std::getline(iss, condition, '"');
            std::getline(iss, condition, '"');
            kvs.SubscribeWithQuery(deviceId, condition);
        } else if (cmd == "UnSubscribeWithQuery") {
            std::string deviceId;
            std::getline(iss, deviceId, '"');
            std::getline(iss, deviceId, '"');
            std::string condition;
            std::getline(iss, condition, '"');
            std::getline(iss, condition, '"');
            kvs.UnSubscribeWithQuery(deviceId, condition);
        } else if (cmd == "SetCapabilityRange") {
            std::string local;
            std::vector <std::string> locals;
            for (int i = 0; i < 2; ++i) {
                std::getline(iss, local, '"');
                std::getline(iss, local, '"');
                locals.push_back(local);
            }
            std::string remote;
            std::vector <std::string> remotes;
            for (int i = 0; i < 2; ++i) {
                std::getline(iss, remote, '"');
                std::getline(iss, remote, '"');
                remotes.push_back(remote);
            }
            kvs.SetCapabilityRange(locals, remotes);

        } else if (cmd == "GetAllKvStoreId") {
            std::string appId;
            std::getline(iss, appId, '"');
            std::getline(iss, appId, '"');
            kvs.GetAllKvStoreId(appId);
        } else if (cmd == "CloseKvStore") {
            std::string appId;
            std::getline(iss, appId, '"');
            std::getline(iss, appId, '"');
            std::string storeId;
            std::getline(iss, storeId, '"');
            std::getline(iss, storeId, '"');
            kvs.CloseKvStore(appId,storeId);
        } else if (cmd == "CloseAllKvStore") {
            std::string appId;
            std::getline(iss, appId, '"');
            std::getline(iss, appId, '"');
            kvs.CloseAllKvStore(appId);
        } else if (cmd == "DeleteKvStore") {
            std::string appId;
            std::getline(iss, appId, '"');
            std::getline(iss, appId, '"');
            std::string storeId;
            std::getline(iss, storeId, '"');
            std::getline(iss, storeId, '"');
            kvs.DeleteKvStore(appId,storeId);
        } else if (cmd == "DeleteAllKvStore") {
            std::string appId;
            std::getline(iss, appId, '"');
            std::getline(iss, appId, '"');
            kvs.DeleteAllKvStore(appId);
        } else if (cmd == "GetLocalDevice") {
            kvs.GetLocalDevice();
        } else if (cmd == "GetSingleKvStore") {
            std::string appId;
            std::getline(iss, appId, '"');
            std::getline(iss, appId, '"');
            std::string storeId;
            std::getline(iss, storeId, '"');
            std::getline(iss, storeId, '"');
            kvs.GetSingleKvStore(appId,storeId);
        } else {
            std::cout << "Unknown command: " << cmd << std::endl;
        }
    }
}


int main(int argc, char const *argv[])
{
    // 使用提示
    // std::cout << "Welcome to Distributed KV Store Demo!" << std::endl;
    // std::cout << "Usage:" << std::endl;
    // std::cout << "- To get a value: get \"key\"" << std::endl;
    // std::cout << "- To put a value: put \"key\" \"value\"" << std::endl;
    // std::cout << "- To exit the program: exit or quit" << std::endl;

    Run();
    return 0;
}
